/*
Copyright 2018 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package sidecar

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"

	"k8s.io/test-infra/prow/gcsupload"
	"k8s.io/test-infra/prow/pod-utils/wrapper"
)

// NewOptions returns an empty Options with no nil fields
func NewOptions() *Options {
	return &Options{
		GcsOptions: gcsupload.NewOptions(),
		// Do not instantiate DeprecatedWrapperOptions by default
	}
}

// Options exposes the configuration necessary
// for defining the process being watched and
// where in GCS an upload will land.
type Options struct {
	GcsOptions               *gcsupload.Options `json:"gcs_options"`
	DeprecatedWrapperOptions *wrapper.Options   `json:"wrapper_options,omitempty"` // TODO(fejta): remove july 2019

	// Additional entries to wait for if set
	Entries []wrapper.Options `json:"entries,omitempty"`

	// EntryError requires all entries to pass in order to exit cleanly.
	EntryError bool `json:"entry_error,omitempty"`

	// IgnoreInterrupts instructs the waiting process to ignore interrupt
	// signals. An interrupt signal is sent to this process when the kubelet
	// decides to delete the test Pod. This may be as a result of:
	//  - the ProwJob exceeding the `default_job_timeout` as configured for Prow
	//  - the ProwJob exceeding the `timeout` as configured for the job itself
	//  - the Pod exceeding the `pod_running_timeout` as configured for Prow
	//  - cluster instability causing the Pod to be evicted
	//
	// When this happens, the `entrypoint` process also gets the signal, and
	// forwards it to the process under test. `entrypoint` will wait for the
	// test process to exit, either configured with:
	//  - `grace_period` in the default decoration configurations for Prow
	//  - `grace_period` in the job's specific configuration
	// After the grace period, `entrypoint` will forcefully terminate the test
	// process and signal to `sidecar` that the process has exited.
	//
	// In parallel, the kubelet will be waiting on the Pod's `terminationGracePeriod`
	// after sending the interrupt signal, at which point the kubelet will forcefully
	// terminate all containers in the Pod.
	//
	// If `ignore_interrupts` is set, `sidecar` will do nothing upon receipt of
	// the interrupt signal; this implicitly means that upload of logs and artifacts
	// will begin when the test process exits, which may be as long as the grace
	// period if the test process does not gracefully handle interrupts. This will
	// require that the user configures the Pod's termination grace period to be
	// longer than the `entrypoint` grace period for the test process and the time
	// taken by `sidecar` to upload all relevant artifacts.
	IgnoreInterrupts bool `json:"ignore_interrupts,omitempty"`
}

func (o Options) entries() []wrapper.Options {
	var e []wrapper.Options
	if o.DeprecatedWrapperOptions != nil {
		e = append(e, *o.DeprecatedWrapperOptions)
	}
	return append(e, o.Entries...)
}

// Validate ensures that the set of options are
// self-consistent and valid
func (o *Options) Validate() error {
	ents := o.entries()
	if len(ents) == 0 {
		return errors.New("no wrapper.Option entries")
	}
	for i, e := range ents {
		if err := e.Validate(); err != nil {
			return fmt.Errorf("entry %d: %v", i, err)
		}
	}

	return o.GcsOptions.Validate()
}

const (
	// JSONConfigEnvVar is the environment variable that
	// utilities expect to find a full JSON configuration
	// in when run.
	JSONConfigEnvVar = "SIDECAR_OPTIONS"
)

// ConfigVar exposese the environment variable used
// to store serialized configuration
func (o *Options) ConfigVar() string {
	return JSONConfigEnvVar
}

// LoadConfig loads options from serialized config
func (o *Options) LoadConfig(config string) error {
	return json.Unmarshal([]byte(config), o)
}

// AddFlags binds flags to options
func (o *Options) AddFlags(flags *flag.FlagSet) {
	o.GcsOptions.AddFlags(flags)
	// DeprecatedWrapperOptions flags should be unused, remove immediately
}

// Complete internalizes command line arguments
func (o *Options) Complete(args []string) {
	o.GcsOptions.Complete(args)
}

// Encode will encode the set of options in the format that
// is expected for the configuration environment variable
func Encode(options Options) (string, error) {
	encoded, err := json.Marshal(options)
	return string(encoded), err
}
